#include "VirtualFileSystem.h"
#include "utils/cuUtils.h"
#include <iostream>
#include <sstream>


namespace z3d{
	namespace utils{
		CVirtualFileSystem::CVirtualFileSystem(void)
			:m_systemResourcesBasePath(".")
			,m_writingAllowedPath(".")
			,VFS_APPENDER("VFS")
			,m_isInitialized(false)

		{
			glog::CGlobalLog::initSingleton();
			m_log = &glog::CGlobalLog::getSingleton();
			m_log->addAppender(VFS_APPENDER);
			// TODO: crear una variable global y una manera de cambiar el nivel de log para todo el sistema
			boost::shared_ptr<glog::IListener> sp(new glog::CLogWriter("engine.log", glog::LOGLEVEL_DEBUG));
			m_log->addListener(sp,VFS_APPENDER);
		}
		
		uint32_t CVirtualFileSystem::pathCreator(const boost::filesystem::path& path_to_create )
		{
			if (!boost::filesystem::exists(path_to_create))
			{
				pathCreator(path_to_create.parent_path());
			}
			try{
				//if not create the directory
				boost::filesystem::create_directory(path_to_create);
				return 0;
			}catch(const boost::filesystem::filesystem_error& fe)
			{
				std::stringstream ss;
				ss << "Error creating file: " << path_to_create.c_str() << "\nError returned is" << fe.what() << "\n";
				THROW_AND_LOG(VFS_APPENDER,core::fileCreationException, ss.str());
				return 1;
			}
		}

		CVirtualFileSystem::~CVirtualFileSystem(void)
		{
			reset();
		}
		int CVirtualFileSystem::init()
		{
			PHYSFS_init(NULL);
			m_isInitialized = true;
			
			return 0;
		}
		int CVirtualFileSystem::addLocationToSearch(const char* location, int order)
		{
			boost::filesystem::path writePath(m_systemResourcesBasePath);
			boost::filesystem::path subPath(location);
			writePath = writePath / subPath;
			std::string s = boost::filesystem::system_complete(writePath).string();
			//const char* fullPath = s.c_str();
			PHYSFS_addToSearchPath(s.c_str(), order);
			return 0;
		}

		void CVirtualFileSystem::setSystemResourcesBasePath(const char* path)
		{
			assertion(m_isInitialized,"Virtual file system is not initilized yet");
			boost::filesystem::path basePath(path);
			if(!exists(basePath))
			{
				basePath = boost::filesystem::system_complete(basePath);
				pathCreator(basePath);
			}
			m_systemResourcesBasePath = boost::filesystem::system_complete(basePath).string();
		}

		int CVirtualFileSystem::setWriteDirectory( const char* location )
		{
			assertion(m_isInitialized,"Virtual file system is not initilized yet");
			boost::filesystem::path writePath(m_systemResourcesBasePath);
			assertion(exists(writePath),"Base path doesn't exists");
			
			
			boost::filesystem::path subPath(location);
			writePath = writePath / subPath;
			pathCreator(writePath);
			m_writingAllowedPath = boost::filesystem::system_complete(writePath).string();
			const char* fullPath = m_writingAllowedPath.c_str();
			PHYSFS_setWriteDir(fullPath);
			
			return 0;
		}

		bool CVirtualFileSystem::checkIfFileExists( const char* fileName )
		{
			if(PHYSFS_exists(fileName)== 0)
				return false;
			return true;
		}
		void CVirtualFileSystem::deleteDeployedFiles()
		{
			const std::vector<const char*> v = m_deployedManager.getKeys();
			for(unsigned int i = 0; i < v.size(); i++)
				deleteFile(v[i]);
		}
		void CVirtualFileSystem::deleteFile(const char* fileName)
		{
			PHYSFS_delete(m_deployedManager.getValue(fileName));
			m_deployedManager.removeItem(fileName);
			assert(!m_deployedManager.exists(fileName));
		}

		void CVirtualFileSystem::reset()
		{
			if(m_isInitialized)
				PHYSFS_deinit();
			m_isInitialized = false;
		}

		const char* CVirtualFileSystem::deployFile( const char* fileName,DEPLOY_MODE mode )
		{
			assert(checkIfFileExists(fileName));
			if (m_deployedManager.exists(fileName))
			{
				return m_deployedManager.getValue(fileName);
			}
			//first open file and put into memory
			PHYSFS_File* fileSource = PHYSFS_openRead(fileName);
			unsigned int fileSize = static_cast<unsigned int>(PHYSFS_fileLength(fileSource));
			char* buffer = new char[fileSize];
			// uint64_t bytesReaded = PHYSFS_read(fileSource,buffer,1,fileSize);
			PHYSFS_read(fileSource,buffer,1,fileSize);
			PHYSFS_close(fileSource);
			
			// depending on the user decision, generate a name of the file
			char* generatedName;
			if(mode == CVirtualFileSystem::DPMOD_SHADOW)
			{
				std::string mdV(md5((char*)fileName));
				generatedName = new char[mdV.length() + 1];
				memcpy(generatedName,mdV.c_str(),mdV.length() + 1);
			}
			if(mode == CVirtualFileSystem::DPMOD_SHADOW_WITH_EXTENSION)
			{
				std::string mdV(fileName);
				// separate the body and the extension to do not shadow the extension
				std::size_t idx = mdV.find(".");
				std::string body = mdV.substr(0, idx);
				std::string ext = mdV.substr(idx + 1, mdV.length());
				body = md5(body.c_str());
				//once separated and shadowed, append again
				body.append(".").append(ext);
				generatedName = new char[body.length() + 1];
				memcpy(generatedName,body.c_str(),body.length() + 1);
				
			}
			//then, write destination file
			PHYSFS_File* fileDest;
			if(mode == CVirtualFileSystem::DPMOD_NORMAL)
			{
				fileDest = PHYSFS_openWrite(fileName);
			}
			else
			{
				fileDest = PHYSFS_openWrite(generatedName);
			}
			PHYSFS_write(fileDest,buffer,1,fileSize);
			PHYSFS_close(fileDest);
			if(mode == CVirtualFileSystem::DPMOD_NORMAL)
			{
				m_deployedManager.insert(fileName,fileName);
			}
			else
			{
				m_deployedManager.insert(fileName,generatedName);
			}
			delete[] buffer;
			//delete[] generatedName;
			return m_deployedManager.getValue(fileName);
			
		}


	};
};